/*
Copyright (C) BABEC. All rights reserved.
Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.

SPDX-License-Identifier: Apache-2.0
*/

package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"strconv"
	"time"

	"chainmaker.org/chainmaker/common/v2/crypto"
	"chainmaker.org/chainmaker/common/v2/crypto/asym"
	"chainmaker.org/chainmaker/common/v2/random/uuid"
	"chainmaker.org/chainmaker/pb-go/v2/common"
	sdk "chainmaker.org/chainmaker/sdk-go/v2"

	//"chainmaker.org/chainmaker/sdk-go/v2/examples"
	sdkutils "chainmaker.org/chainmaker/sdk-go/v2/utils"
)

var (
	claimContractName = fmt.Sprintf("claim%d", time.Now().UnixNano())
	claimVersion      = "v2.0.0"
	claimByteCodePath = "rust-fact-2.0.0.wasm"

	sdkConfigOrg1Client1Path = "sdk_config.yml"

	secondSignerCertFilePath    = "config/TestCMorg2/certs/user/cmtestuser2/cmtestuser2.sign.crt"
	secondSignerPrivKeyFilePath = "config/TestCMorg2/certs/user/cmtestuser2/cmtestuser2.sign.key"
	secondSignerOrgId           = "TestCMorg2"
)

const (
	UserNameOrg1Admin1 = "cmtestuser1"
	UserNameOrg2Admin1 = "cmtestuser2"
	UserNameOrg3Admin1 = "cmtestuser3"
	UserNameOrg4Admin1 = "cmtestuser4"

	OrgId1 = "TestCMorg1"
	OrgId2 = "TestCMorg2"
	OrgId3 = "TestCMorg3"
	OrgId4 = "TestCMorg4"
)

var permissionedPkUsers = map[string]*PermissionedPkUsers{
	"cmtestuser1": {
		"config/TestCMorg1/certs/user/cmtestuser1/cmtestuser1.sign.key",
		OrgId1,
	},
	"cmtestuser2": {
		"config/TestCMorg2/certs/user/cmtestuser2/cmtestuser2.sign.key",
		OrgId2,
	},
	"cmtestuser3": {
		"config/TestCMorg3/certs/user/cmtestuser3/cmtestuser3.sign.key",
		OrgId1,
	},
	"cmtestuser4": {
		"config/TestCMorg4/certs/user/cmtestuser4/cmtestuser4.sign.key",
		OrgId2,
	},
}

var pkUsers = map[string]*PkUsers{
	"cmtestuser1": {
		"config/TestCMorg1/certs/user/cmtestuser1/cmtestuser1.sign.key",
	},
	"cmtestuser2": {
		"config/TestCMorg2/certs/user/cmtestuser2/cmtestuser2.sign.key",
	},
	"cmtestuser3": {
		"config/TestCMorg3/certs/user/cmtestuser3/cmtestuser3.sign.key",
	},
	"cmtestuser4": {
		"config/TestCMorg4/certs/user/cmtestuser4/cmtestuser4.sign.key",
	},
}

var users = map[string]*User{
	"cmtestuser1": {
		"config/TestCMorg1/certs/user/cmtestuser1/cmtestuser1.tls.key",
		"config/TestCMorg1/certs/user/cmtestuser1/cmtestuser1.tls.crt",
		"config/TestCMorg1/certs/user/cmtestuser1/cmtestuser1.sign.key",
		"config/TestCMorg1/certs/user/cmtestuser1/cmtestuser1.sign.crt",
	},
	"cmtestuser2": {
		"config/TestCMorg2/certs/user/cmtestuser2/cmtestuser2.tls.key",
		"config/TestCMorg2/certs/user/cmtestuser2/cmtestuser2.tls.crt",
		"config/TestCMorg2/certs/user/cmtestuser2/cmtestuser2.sign.key",
		"config/TestCMorg2/certs/user/cmtestuser2/cmtestuser2.sign.crt",
	},
	"cmtestuser3": {
		"config/TestCMorg3/certs/user/cmtestuser3/cmtestuser3.tls.key",
		"config/TestCMorg3/certs/user/cmtestuser3/cmtestuser3.tls.crt",
		"config/TestCMorg3/certs/user/cmtestuser3/cmtestuser3.sign.key",
		"config/TestCMorg3/certs/user/cmtestuser3/cmtestuser3.sign.crt",
	},
	"cmtestuser4": {
		"config/TestCMorg4/certs/user/cmtestuser4/cmtestuser4.tls.key",
		"config/TestCMorg4/certs/user/cmtestuser4/cmtestuser4.tls.crt",
		"config/TestCMorg4/certs/user/cmtestuser4/cmtestuser4.sign.key",
		"config/TestCMorg4/certs/user/cmtestuser4/cmtestuser4.sign.crt",
	},
}

type PkUsers struct {
	SignKeyPath string
}

type PermissionedPkUsers struct {
	SignKeyPath string
	OrgId       string
}

type User struct {
	TlsKeyPath, TlsCrtPath   string
	SignKeyPath, SignCrtPath string
}

func main() {
	cc, err := sdk.NewChainClient(
		sdk.WithConfPath(sdkConfigOrg1Client1Path),
	)
	if err != nil {
		log.Fatalln(err)
	}

	fmt.Println("====================== 创建合约 ======================")
	usernames := []string{UserNameOrg1Admin1, UserNameOrg2Admin1, UserNameOrg3Admin1, UserNameOrg4Admin1}
	testCreateUserContract(cc, claimContractName, claimVersion, claimByteCodePath,
		common.RuntimeType_WASMER, []*common.KeyValuePair{}, true, usernames...)

	fmt.Println("====================== 使用其他signer调用合约 ======================")
	certPem, err := ioutil.ReadFile(secondSignerCertFilePath)
	if err != nil {
		log.Fatalln(err)
	}
	cert, err := sdkutils.ParseCert(certPem)
	if err != nil {
		log.Fatalln(err)
	}
	privKeyPem, err := ioutil.ReadFile(secondSignerPrivKeyFilePath)
	if err != nil {
		log.Fatalln(err)
	}
	privateKey, err := asym.PrivateKeyFromPEM(privKeyPem, nil)
	if err != nil {
		log.Fatalln(err)
	}

	signer := &sdk.CertModeSigner{
		PrivateKey: privateKey,
		Cert:       cert,
		OrgId:      secondSignerOrgId,
	}
	_, err = testInvokeContractBySigner(cc, "save", true, signer)
	if err != nil {
		log.Println(err)
	}
}

func GetEndorsersWithAuthType(hashType crypto.HashType, authType sdk.AuthType, payload *common.Payload, usernames ...string) ([]*common.EndorsementEntry, error) {
	var endorsers []*common.EndorsementEntry

	for _, name := range usernames {
		var entry *common.EndorsementEntry
		var err error
		switch authType {
		case sdk.PermissionedWithCert:
			u, ok := users[name]
			if !ok {
				return nil, errors.New("user not found")
			}
			if sdk.GetP11Handle() != nil || sdk.KMSEnabled() {
				entry, err = sdkutils.MakeEndorserWithPathAndP11Handle(u.SignKeyPath, u.SignCrtPath, sdk.GetP11Handle(),
					sdk.KMSEnabled(), payload)
				if err != nil {
					return nil, err
				}
			} else {
				entry, err = sdkutils.MakeEndorserWithPath(u.SignKeyPath, u.SignCrtPath, payload)
				if err != nil {
					return nil, err
				}
			}

		case sdk.PermissionedWithKey:
			u, ok := permissionedPkUsers[name]
			if !ok {
				return nil, errors.New("user not found")
			}
			entry, err = sdkutils.MakePkEndorserWithPath(u.SignKeyPath, hashType, u.OrgId, payload)
			if err != nil {
				return nil, err
			}

		case sdk.Public:
			u, ok := pkUsers[name]
			if !ok {
				return nil, errors.New("user not found")
			}
			entry, err = sdkutils.MakePkEndorserWithPath(u.SignKeyPath, hashType, "", payload)
			if err != nil {
				return nil, err
			}

		default:
			return nil, errors.New("invalid authType")
		}
		endorsers = append(endorsers, entry)
	}

	return endorsers, nil
}

func testCreateUserContract(client *sdk.ChainClient, contractName, version, byteCodePath string, runtime common.RuntimeType,
	kvs []*common.KeyValuePair, withSyncResult bool, usernames ...string) {

	payload, err := client.CreateContractCreatePayload(contractName, version, byteCodePath, runtime, kvs)
	if err != nil {
		log.Fatalln(err)
	}

	//endorsers, err := examples.GetEndorsers(payload, usernames...)
	endorsers, err := GetEndorsersWithAuthType(client.GetHashType(),
		client.GetAuthType(), payload, usernames...)
	if err != nil {
		log.Fatalln(err)
	}

	resp, err := client.SendContractManageRequest(payload, endorsers, -1, withSyncResult)
	if err != nil {
		log.Fatalln(err)
	}

	err = CheckProposalRequestResp(resp, true)
	if err != nil {
		log.Fatalln(err)
	}

	fmt.Printf("CREATE claim contract resp: %+v\n", resp)
}

func CheckProposalRequestResp(resp *common.TxResponse, needContractResult bool) error {
	if resp.Code != common.TxStatusCode_SUCCESS {
		if resp.Message == "" {
			resp.Message = resp.Code.String()
		}
		return errors.New(resp.Message)
	}

	if needContractResult && resp.ContractResult == nil {
		return fmt.Errorf("contract result is nil")
	}

	if resp.ContractResult != nil && resp.ContractResult.Code != 0 {
		return errors.New(resp.ContractResult.Message)
	}

	return nil
}

func testInvokeContractBySigner(client *sdk.ChainClient,
	method string, withSyncResult bool, signer sdk.Signer) (string, error) {

	curTime := strconv.FormatInt(time.Now().Unix(), 10)
	fileHash := uuid.GetUUID()
	kvs := []*common.KeyValuePair{
		{
			Key:   "time",
			Value: []byte(curTime),
		},
		{
			Key:   "file_hash",
			Value: []byte(fileHash),
		},
		{
			Key:   "file_name",
			Value: []byte(fmt.Sprintf("file_%s", curTime)),
		},
	}

	resp, err := client.InvokeContractBySigner(claimContractName, method, "", kvs, -1,
		withSyncResult, nil, signer)
	if err != nil {
		return "", err
	}

	if resp.Code != common.TxStatusCode_SUCCESS {
		return "", fmt.Errorf("invoke contract failed, [code:%d]/[msg:%s]\n", resp.Code, resp.Message)
	}

	if !withSyncResult {
		fmt.Printf("invoke contract success, resp: [code:%d]/[msg:%s]/[txId:%s]\n", resp.Code, resp.Message, resp.ContractResult.Result)
	} else {
		fmt.Printf("invoke contract success, resp: [code:%d]/[msg:%s]/[contractResult:%s]\n", resp.Code, resp.Message, resp.ContractResult)
	}
	return fileHash, nil
	fmt.
}
